package net.buffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class ByteBuffer {

    private byte[] buffer;
    private int offset;
    private int length;
    private int position;

    private byte[] buffer() {
        return buffer;
    }

    private int offset() {
        return offset;
    }

    private int length() {
        return length;
    }

    private int position() {
        return position;
    }

    private void position(int position) {
        if (position > limit) throw new RuntimeException("position > limit !");
        this.position = position;
    }

    private int limit() {
        return limit;
    }

    private void limit(int limit) {
        if (position > limit) throw new RuntimeException("position > limit !");
        this.limit = limit;
    }

    private int limit;
    private boolean isRead;

    public static ByteBuffer from(byte[] data) {
        ByteBuffer byteBuffer = new ByteBuffer(data);
        byteBuffer.isRead = true;
        return byteBuffer;
    }

    public ByteBuffer(int capacity) {
        buffer = new byte[capacity];
        offset = 0;
        length = capacity;
        clear();
    }

    public ByteBuffer(byte[] buffer) {
        this.buffer = buffer;
        offset = 0;
        length = buffer.length;
        clear();
    }

    public ByteBuffer(byte[] buffer, int offset, int length) {
        this.buffer = buffer;
        this.offset = offset;
        this.length = length;
        clear();
    }

    public ByteBuffer read() {
        if (!isRead) {
            isRead = true;
            limit = position;
            position = 0;
        }
        return this;
    }

    public int getInt() {
        if (!readable()) throw new RuntimeException("Please use read() before get value");
        if (remaining() < 4) throw new RuntimeException("get remaining less 4!");

//        System.out.println("getInt" + buffer);

        return (buffer[offset + (position++)] & (0xFF)) << 24
                | (buffer[offset + (position++)] & (0xFF)) << 16
                | (buffer[offset + (position++)] & (0xFF)) << 8
                | (buffer[offset + (position++)] & (0xFF));
    }

    public String getString() {
        String str = new String(buffer(), position() + offset(), remaining(), StandardCharsets.UTF_8);
        position(limit());
        return str;
    }

    public ByteBuffer putInt(int value) {
        if (!writeable()) throw new RuntimeException("Please use write() or clear() before put value");
        if (remaining() < 4) throw new RuntimeException("put remaining less 4!");
        buffer[offset + (position++)] = (byte) (value >> 24);
        buffer[offset + (position++)] = (byte) (value >> 16);
        buffer[offset + (position++)] = (byte) (value >> 8);
        buffer[offset + (position++)] = (byte) (value);
        return this;
    }

    public int put(InputStream stream) throws IOException {
        if(remaining() <= 0)
        {
            throw new ArrayIndexOutOfBoundsException("bytebuffer hasn't enough room to put stream!");
        }
        int readCount = stream.read(buffer(), offset() + position(), remaining());
        if(readCount > 0)
        {
            position(position() + readCount);
        }
        return readCount;
    }

    public void get(OutputStream os) throws IOException {
        os.write(buffer(), offset() + position(), remaining());
        position(limit());
    }

    private boolean readable() {
        return isRead;
    }

    private boolean writeable() {
        return !isRead;
    }

    public ByteBuffer fill(ByteBuffer buffer) {
        if (buffer == this) {
            throw new RuntimeException("put buffer should not self!");
        }
        if (!buffer.readable()) throw new RuntimeException("buffer is not readable! You should can call read() first.");

        int count = Math.min(this.remaining(), buffer.remaining());
        System.arraycopy(buffer.buffer(), buffer.offset() + buffer.position(),
                this.buffer(), this.position() + this.offset(), count);
        buffer.position(buffer.position() + count);
        this.position(this.position() + count);
        return this;
    }

    public ByteBuffer write() {

        if (isRead) {
            isRead = false;
            if (position() > 0) {
                System.arraycopy(buffer, offset + position, buffer, 0, remaining());
                position = remaining();
            }
            limit = length;
        }
        return this;
    }

    public ByteBuffer clear() {
        isRead = false;
        position = 0;
        limit = length;
        return this;
    }

    public int remaining() {
        int rem = limit - position;
        return Math.max(rem, 0);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[" + buffer[0]);
        for (int i = 1; i < buffer.length; i++) {
            sb.append(",");
            sb.append(buffer[i]);
        }
        sb.append("]")
                .append(" position: ").append(position())
                .append(" limit: ").append(limit())
                .append(" sate: ").append(isRead ? "read" : "write");
        return sb.toString();
    }
}
